package cn.yto.print.service;

import cn.hutool.json.JSONUtil;
import cn.yto.print.config.PrintFutureContext;
import cn.yto.print.config.ThreadPoolManager;
import cn.yto.print.config.i18n.I18nResourceManager;
import cn.yto.print.controller.IndexController;
import cn.yto.print.enums.MessageTypeEnum;
import cn.yto.print.enums.PrintCmdEnum;
import cn.yto.print.enums.PrintHandlerEnum;
import cn.yto.print.enums.ResponseCodeEnum;
import cn.yto.print.exception.PluginCommException;
import cn.yto.print.exception.PluginHandleException;
import cn.yto.print.model.request.PrintTaskRequest;
import cn.yto.print.model.response.PrintTaskResponse;
import cn.yto.print.plugins.PrintResourceFactory;
import cn.yto.print.service.handle.MessageHandler;
import cn.yto.print.sqlite.service.IPrintHistoryService;
import cn.yto.print.system.SpringContextHolder;
import cn.yto.print.utils.CommonUtil;
import cn.yto.print.utils.PrintTaskUtil;
import cn.yto.print.utils.WebSocketUtil;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Slf4j
@Service
public class PrintConsumerService {

    /**
     * 任务执行超时时间（秒）
     */
    private static final long TIMEOUT_SECONDS = 10L;

    private final boolean isRunning = true;

    private ScheduledExecutorService executorService;

    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private IPrintHistoryService printHistoryService;
    @Autowired
    private PrintResourceFactory printResourceFactory;
    @Autowired
    private IndexController indexController;

    @PostConstruct
    public void initAndStartConsumer() {
        executorService = ThreadPoolManager.getSingleDaemonExecutor();
        // 开启任务消费
        executorService.execute(this::consumeLoop);
    }

    /**
     * 消费任务
     */
    private void consumeLoop() {
        while (isRunning) {
            PrintTaskRequest task = null;
            try {
                // 阻塞获取任务
                task = takeTaskFromQueue();
                long startTime = System.currentTimeMillis();
                log.info("================== 打印任务队列消息消费开始，任务ID：{}，任务大小：{} ==================",
                        task.getTaskId(), task.getDocuments().size());
                handleTask(task);
                log.info("================== 打印任务队列消息消费结束，任务ID：{}，处理耗时：{}ms ==================",
                        task.getTaskId(), System.currentTimeMillis() - startTime);
            } catch (Exception ex) {
                if (!(ex instanceof InterruptedException)) {
                    log.error("【打印任务消费线程处理异常，任务可能会丢失】", ex);
                    handleExceptionResponse(task);
                }
            }
        }
    }

    private PrintTaskRequest takeTaskFromQueue() throws InterruptedException {
        LinkedBlockingQueue<PrintTaskRequest> queue = printResourceFactory.getGlobalTaskQueue();
        return queue.take();
    }

    /**
     * 处理打印任务
     */
    private void handleTask(PrintTaskRequest request) {
        List<List<PrintTaskRequest.Document>> groupedDocs = PrintTaskUtil.groupConsecutiveByPrinter(
                request.getPrinter(), request.getDocuments());
        List<PrintTaskResponse> accumulatedResponses = new CopyOnWriteArrayList<>();
        AtomicInteger docIndex = new AtomicInteger(
                request.getFirstDocumentNumber() == 0 ? 1 : request.getFirstDocumentNumber());
        for (int groupIndex = 0; groupIndex < groupedDocs.size(); groupIndex++) {
            List<PrintTaskRequest.Document> docGroup = groupedDocs.get(groupIndex);
            processSingleGroup(request, docGroup, docIndex, groupIndex, groupedDocs, accumulatedResponses);
        }
    }

    /**
     * 处理分组任务
     */
    private void processSingleGroup(PrintTaskRequest originalTask, List<PrintTaskRequest.Document> docsGroup, AtomicInteger firstDocNumber,
                                    int currentIndex, List<List<PrintTaskRequest.Document>> allGroups, List<PrintTaskResponse> allResponses) {
        PrintHandlerEnum handlerEnum = PrintHandlerEnum.getPrintTypeEnum(docsGroup.get(0).getExpressPrintType());
        PrintTaskRequest groupedTask = createGroupTask(originalTask, docsGroup, firstDocNumber);
        try {
            MessageHandler handler = (MessageHandler) applicationContext.getBean(handlerEnum.getBeanClass());
            CompletableFuture<PrintTaskResponse> future = handler.processMessage(groupedTask);
            PrintTaskResponse response = waitForResponse(future);
            log.info("第 {}/{} 分组打印结果通知：{}", currentIndex + 1, allGroups.size(), JSONUtil.toJsonStr(response));
            allResponses.add(response);
            if (isLastBatch(docsGroup, allGroups)) {
                handleFinalBatchProcess(allResponses, originalTask);
            }
        } catch (Exception ex) {
            log.error("第 {}/{} 分组打印发生异常", currentIndex + 1, allGroups.size(), ex);
            allResponses.add(buildExceptionResponse(originalTask, docsGroup, ex));
            if (isLastBatch(docsGroup, allGroups)) {
                handleFinalBatchProcess(allResponses, originalTask);
            }
        } finally {
            PrintFutureContext.removeResponseFuture(groupedTask.getTaskId());
        }
    }

    /**
     * 创建分组任务
     */
    private PrintTaskRequest createGroupTask(PrintTaskRequest original, List<PrintTaskRequest.Document> groupDocs, AtomicInteger docCounter) {
        PrintTaskRequest groupedTask = new PrintTaskRequest();
        groupedTask.setSessionId(original.getSessionId());
        groupedTask.setCmd(original.getCmd());
        groupedTask.setTaskId(original.getTaskId() + "-" + PrintTaskUtil.generateRandom());
        groupedTask.setPrinter(groupDocs.get(0).getPrinter());
        groupedTask.setTemplateType(StringUtils.defaultString(original.getTemplateType(), ""));
        groupedTask.setFirstDocumentNumber(docCounter.get());
        docCounter.addAndGet(groupDocs.size());
        groupedTask.setTotalDocumentCount(original.getTotalDocumentCount() == 0 ?
                original.getDocuments().size() : original.getTotalDocumentCount());
        groupedTask.setDocuments(groupDocs);
        return groupedTask;
    }

    private PrintTaskResponse waitForResponse(CompletableFuture<PrintTaskResponse> future) {
        try {
            return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            throw new PluginHandleException("组件打印超时", e);
        } catch (Exception e) {
            throw new PluginHandleException("组件打印异常", e);
        } finally {
            future.cancel(true);
        }
    }

    /**
     * 是否最后一批
     */
    private boolean isLastBatch(List<PrintTaskRequest.Document> current, List<List<PrintTaskRequest.Document>> all) {
        return current == all.get(all.size() - 1);
    }

    private void handleFinalBatchProcess(List<PrintTaskResponse> responses, PrintTaskRequest original) {
        PrintTaskResponse summaryResp = new PrintTaskResponse();
        summaryResp.setCodeProperty(ResponseCodeEnum.SUCCESS);
        summaryResp.setSessionId(original.getSessionId());
        summaryResp.setCmd(PrintCmdEnum.NOTIFY_PRINT_RESULT.getCmd());
        summaryResp.setTaskId(original.getTaskId());
        summaryResp.setPrinter(original.getPrinter());

        List<PrintTaskResponse.PrintStatus> failedList = responses.stream()
                .filter(r -> !CollectionUtils.isEmpty(r.getPrintStatus()))
                .flatMap(r -> r.getPrintStatus().stream()
                        .filter(x -> ResponseCodeEnum.FAILURE.getStatus().equals(x.getStatus())))
                .collect(Collectors.toList());

        summaryResp.setPrintStatus(failedList);
        List<String> failOrder = failedList.stream().map(PrintTaskResponse.PrintStatus::getDocumentID).collect(Collectors.toList());
        printHistoryService.updatePrintHistory(failOrder, 2);

        List<String> successOrder = original.getDocuments().stream()
                .map(PrintTaskRequest.Document::getDocumentID)
                .filter(id -> failedList.stream().noneMatch(err -> err.getDocumentID().equals(id)))
                .collect(Collectors.toList());

        summaryResp.setSuccess(successOrder);
        summaryResp.setTotal(original.getDocuments().size() + "-" + successOrder.size());
        printHistoryService.updatePrintHistory(successOrder, 1);

        summaryResp.setTemplateType(original.getTemplateType());
        WebSocketUtil.sendCacheMessage(summaryResp);

        String msg = String.format(I18nResourceManager.getInstance().get("index.console.result"),
                successOrder.size(), failOrder.size());
        indexController.addConsoleLog(msg, MessageTypeEnum.INFO);
    }

    private static PrintTaskResponse buildExceptionResponse(PrintTaskRequest original, List<PrintTaskRequest.Document> docs, Exception ex) {
        PrintTaskResponse resp = new PrintTaskResponse();
        resp.setCmd(PrintCmdEnum.NOTIFY_PRINT_RESULT.getCmd());
        resp.setTaskId(original.getTaskId());
        resp.setPrinter(original.getPrinter());
        resp.setSessionId(original.getSessionId());
        resp.setTemplateType(original.getTemplateType());

        List<PrintTaskResponse.PrintStatus> statusList = new ArrayList<>();
        for (PrintTaskRequest.Document doc : docs) {
            PrintTaskResponse.PrintStatus status = new PrintTaskResponse.PrintStatus();
            status.setStatus(ResponseCodeEnum.FAILURE.getStatus());
            status.setDocumentID(doc.getDocumentID());
            status.setMsg(ex instanceof PluginCommException ? ((PluginCommException) ex).getMsg()
                    : ex instanceof PluginHandleException ? ((PluginHandleException) ex).getMsg()
                    : "任务处理异常");
            statusList.add(status);
        }
        resp.setPrintStatus(statusList);
        return resp;
    }

    public static void handleExceptionResponse(PrintTaskRequest task) {
        if (task != null) {
            PrintTaskResponse resp = new PrintTaskResponse();
            resp.setCommProperty(ResponseCodeEnum.EXCEPTION);
            resp.setSessionId(task.getSessionId());
            resp.setCmd(PrintCmdEnum.NOTIFY_PRINT_RESULT.getCmd());
            resp.setTaskId(task.getTaskId());
            resp.setPrinter(task.getPrinter());
            WebSocketUtil.sendCacheMessage(resp);
        }
    }

    /**
     * 处理超时响应
     */
    public static void handleTimeOutResponse(PrintTaskResponse resp) {
        if (!CollectionUtils.isEmpty(resp.getPrintStatus())) {
            IPrintHistoryService historyService = SpringContextHolder.getBean(IPrintHistoryService.class);

            List<PrintTaskResponse.PrintStatus> failed = resp.getPrintStatus().stream()
                    .filter(x -> ResponseCodeEnum.FAILURE.getStatus().equals(x.getStatus()))
                    .collect(Collectors.toList());
            resp.setPrintStatus(Lists.newArrayList(failed));
            List<String> failedIds = failed.stream().map(PrintTaskResponse.PrintStatus::getDocumentID).collect(Collectors.toList());
            historyService.updatePrintHistory(failedIds, 2);

            List<String> successIds = resp.getPrintStatus().stream()
                    .filter(x -> ResponseCodeEnum.SUCCESS.getStatus().equals(x.getStatus()))
                    .map(PrintTaskResponse.PrintStatus::getDocumentID)
                    .collect(Collectors.toList());
            resp.setSuccess(successIds);
            resp.setTotal(resp.getPrintStatus().size() + "-" + successIds.size());
            historyService.updatePrintHistory(successIds, 1);
        }
        resp.setTaskId(resp.getTaskId().split("-")[0]);
        resp.setCode(ResponseCodeEnum.TIME_OUT.getCode());
        WebSocketUtil.sendCacheMessage(resp);
    }

}
